/*
 * Rational.java
 *
 * This class is public domain software - that is, you can do whatever you want
 * with it, and include it software that is licensed under the GNU or the
 * BSD license, or whatever other licence you choose, including proprietary
 * closed source licenses.  Similarly, I release this Java version under the
 * same license, though I do ask that you leave this header in tact.
 *
 * If you make modifications to this code that you think would benefit the
 * wider community, please send me a copy and I'll post it on my site.
 *
 * If you make use of this code, I'd appreciate hearing about it.
 *   drew.noakes@drewnoakes.com
 * Latest version of this software kept at
 *   http://drewnoakes.com/
 *
 * Created on 6 May 2002, 18:06
 * Updated 26 Aug 2002 by Drew
 * - Added toSimpleString() method, which returns a simplified and hopefully more
 *   readable version of the Rational.  i.e. 2/10 -> 1/5, and 10/2 -> 5
 * Modified 29 Oct 2002 (v1.2)
 * - Improved toSimpleString() to factor more complex rational numbers into
 *   a simpler form
 *     i.e.
 *       10/15 -> 2/3
 * - toSimpleString() now accepts a boolean flag, 'allowDecimals' which will
 *   display the rational number in decimal form if it fits within 5 digits
 *     i.e.
 *       3/4 -> 0.75 when allowDecimal == true
 */
package com.drew.lang;

import java.io.Serializable;

/**
 * Immutable class for holding a rational number without loss of precision. Provides a familiar representation via toString() in form <code>
 * numerator/denominator</code>.
 *
 * @author  Drew Noakes http://drewnoakes.com
 */
public class Rational extends java.lang.Number implements Serializable
{
  private static final long serialVersionUID = 7451357535940153070L;

  /** Holds the numerator. */
  private final int numerator;

  /** Holds the denominator. */
  private final int denominator;
  private int       maxSimplificationCalculations = 1000;

  /**
   * Creates a new instance of Rational. Rational objects are immutable, so once you've set your numerator and denominator values here, you're stuck
   * with them!
   */
  public Rational(int numerator, int denominator)
  {
    this.numerator   = numerator;
    this.denominator = denominator;
  }

  /**
   * Returns the value of the specified number as a <code>double</code>. This may involve rounding.
   *
   * @return  the numeric value represented by this object after conversion to type <code>double</code>.
   */
  public double doubleValue()
  {
    return (double) numerator / (double) denominator;
  }

  /**
   * Returns the value of the specified number as a <code>float</code>. This may involve rounding.
   *
   * @return  the numeric value represented by this object after conversion to type <code>float</code>.
   */
  public float floatValue()
  {
    return (float) numerator / (float) denominator;
  }

  /**
   * Returns the value of the specified number as a <code>byte</code>. This may involve rounding or truncation. This implementation simply casts the
   * result of <code>doubleValue()</code> to <code>byte</code>.
   *
   * @return  the numeric value represented by this object after conversion to type <code>byte</code>.
   */
  public final byte byteValue()
  {
    return (byte) doubleValue();
  }

  /**
   * Returns the value of the specified number as an <code>int</code>. This may involve rounding or truncation. This implementation simply casts the
   * result of <code>doubleValue()</code> to <code>int</code>.
   *
   * @return  the numeric value represented by this object after conversion to type <code>int</code>.
   */
  public final int intValue()
  {
    return (int) doubleValue();
  }

  /**
   * Returns the value of the specified number as a <code>long</code>. This may involve rounding or truncation. This implementation simply casts the
   * result of <code>doubleValue()</code> to <code>long</code>.
   *
   * @return  the numeric value represented by this object after conversion to type <code>long</code>.
   */
  public final long longValue()
  {
    return (long) doubleValue();
  }

  /**
   * Returns the value of the specified number as a <code>short</code>. This may involve rounding or truncation. This implementation simply casts the
   * result of <code>doubleValue()</code> to <code>short</code>.
   *
   * @return  the numeric value represented by this object after conversion to type <code>short</code>.
   */
  public final short shortValue()
  {
    return (short) doubleValue();
  }

  /** Returns the denominator. */
  public final int getDenominator()
  {
    return this.denominator;
  }

  /** Returns the numerator. */
  public final int getNumerator()
  {
    return this.numerator;
  }

  /**
   * Returns the reciprocal value of this obejct as a new Rational.
   *
   * @return  the reciprocal in a new object
   */
  public Rational getReciprocal()
  {
    return new Rational(this.denominator, this.numerator);
  }

  /** Checks if this rational number is an Integer, either positive or negative. */
  public boolean isInteger()
  {
    return (denominator == 1) || ((denominator != 0) && ((numerator % denominator) == 0)) || ((denominator == 0) && (numerator == 0));
  }

  /**
   * Returns a string representation of the object of form <code>numerator/denominator</code>.
   *
   * @return  a string representation of the object.
   */
  public String toString()
  {
    return numerator + "/" + denominator;
  }

  /** Returns the simplest represenation of this Rational's value possible. */
  public String toSimpleString(boolean allowDecimal)
  {
    if ((denominator == 0) && (numerator != 0))
    {
      return toString();
    }
    else if (isInteger())
    {
      return Integer.toString(intValue());
    }
    else if ((numerator != 1) && ((denominator % numerator) == 0))
    {
      // common factor between denominator and numerator
      int newDenominator = denominator / numerator;

      return new Rational(1, newDenominator).toSimpleString(allowDecimal);
    }
    else
    {
      Rational simplifiedInstance = getSimplifiedInstance();

      if (allowDecimal)
      {
        String doubleString = Double.toString(simplifiedInstance.doubleValue());

        if (doubleString.length() < 5)
        {
          return doubleString;
        }
      }

      return simplifiedInstance.toString();
    }
  }

  /**
   * Decides whether a brute-force simplification calculation should be avoided by comparing the maximum number of possible calculations with some
   * threshold.
   *
   * @return  true if the simplification should be performed, otherwise false
   */
  private boolean tooComplexForSimplification()
  {
    double maxPossibleCalculations = (((double) (Math.min(denominator, numerator) - 1) / 5d) + 2);

    return maxPossibleCalculations > maxSimplificationCalculations;
  }

  /**
   * Compares two <code>Rational</code> instances, returning true if they are mathematically equivalent.
   *
   * @param   obj  the Rational to compare this instance to.
   *
   * @return  true if instances are mathematically equivalent, otherwise false. Will also return false if <code>obj</code> is not an instance of
   *          <code>Rational</code>.
   */
  public boolean equals(Object obj)
  {
    if (!(obj instanceof Rational))
    {
      return false;
    }

    Rational that = (Rational) obj;

    return this.doubleValue() == that.doubleValue();
  }

  /**
   * <p>Simplifies the Rational number.</p>
   *
   * <p>Prime number series: 1, 2, 3, 5, 7, 9, 11, 13, 17</p>
   *
   * <p>To reduce a rational, need to see if both numerator and denominator are divisible by a common factor. Using the prime number series in
   * ascending order guarantees the minimun number of checks required.</p>
   *
   * <p>However, generating the prime number series seems to be a hefty task. Perhaps it's simpler to check if both d & n are divisible by all numbers
   * from 2 -> (Math.min(denominator, numerator) / 2). In doing this, one can check for 2 and 5 once, then ignore all even numbers, and all numbers
   * ending in 0 or 5. This leaves four numbers from every ten to check.</p>
   *
   * <p>Therefore, the max number of pairs of modulus divisions required will be:</p>
   *
   * <pre><code>
        4   Math.min(denominator, numerator) - 1
       -- * ------------------------------------ + 2
       10                    2

       Math.min(denominator, numerator) - 1
     = ------------------------------------ + 2
                      5
     </code></pre>
   *
   * @return  a simplified instance, or if the Rational could not be simpliffied, returns itself (unchanged)
   */
  public Rational getSimplifiedInstance()
  {
    if (tooComplexForSimplification())
    {
      return this;
    }

    for (int factor = 2; factor <= Math.min(denominator, numerator); factor++)
    {
      if ((((factor % 2) == 0) && (factor > 2)) || (((factor % 5) == 0) && (factor > 5)))
      {
        continue;
      }

      if (((denominator % factor) == 0) && ((numerator % factor) == 0))
      {
        // found a common factor
        return new Rational(numerator / factor, denominator / factor);
      }
    }

    return this;
  }
}
